﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;

namespace Perovich.GameObjects.Helpers
{

    /// <summary>
    /// A class used by the <see cref="MouseHelper"/> to expose events.
    /// </summary>
    public class MouseHandler
    {
        /// <summary>
        /// Triggered when the associated button is pressed.
        /// </summary>
        public event EventHandler<MouseHelperEventArgs> Down;
        /// <summary>
        /// Triggered when the associated button is released.
        /// </summary>
        public event EventHandler<MouseHelperEventArgs> Up;
        internal void SignalDown(object sender,MouseState state, MouseState lastState)
        {
            if (Down != null)
                Down(sender, new MouseHelperEventArgs(state, lastState));
        }
        internal void SignalUp(object sender, MouseState state, MouseState lastState)
        {
            if (Up != null)
                Up(sender, new MouseHelperEventArgs(state, lastState));
        }
        internal MouseHandler()
        {
        }
    }
    /// <summary>
    /// Provides data for <see cref="MouseHelper"/> events.
    /// </summary>
    public class MouseHelperEventArgs : EventArgs
    {
        MouseState state;
        MouseState last_state;

        /// <summary>
        /// Gets the <see cref="MouseState"/> associated with this event.
        /// </summary>
        public MouseState State
        {
            get { return state; }
        }

        /// <summary>
        /// Gets the previous <see cref="MouseState"/> associated with this event.
        /// </summary>
        public MouseState LastState
        {
            get
            {
                return last_state;
            }
        }

        /// <summary>
        /// Constructs a new instance of the <see cref=" MouseHelperEventArgs"/> class.
        /// </summary>
        /// <param name="state"></param>
        /// <param name="lastState"></param>
        public MouseHelperEventArgs(MouseState state, MouseState lastState)
        {
            this.state = state;
            this.last_state = lastState;
        }
    }

    public sealed class InternalMouseHelper
    {
        private static Dictionary<object, KeyValuePair<MouseState, MouseState>> ComponentsAndStates = new Dictionary<object, KeyValuePair<MouseState, MouseState>>();
        public static void Init(object c)
        {
            var state = Mouse.GetState();
            ComponentsAndStates.Add(c, new KeyValuePair<MouseState, MouseState>(state, state));
        }
        public static void Fin(object c)
        {
            ComponentsAndStates.Remove(c);
        }
        public static bool HasMouseChanged(object caller, out MouseState ls, out MouseState sta)
        {
            var s = ComponentsAndStates[caller];
            var state = s.Value;
            var last_state = s.Key;
            last_state = state;
            state = Mouse.GetState();

            if (last_state != state)
            {
                var change = new KeyValuePair<MouseState, MouseState>(last_state, state);
                ls = change.Key;
                sta = change.Value;
                ComponentsAndStates[caller] = change;
                return true;
            }
            else
            {
                var change = new KeyValuePair<MouseState, MouseState>(last_state, state);
                ls = change.Key;
                sta = change.Value;
                ComponentsAndStates[caller] = change;
                return false;
            }


        }
    }



    /// <summary>
    /// A singelton class that encapsulates collecting and responging to mouse activity.
    /// </summary>
    /// <remarks>Initialize must be called before using the Instance field.</remarks>
    public sealed class MouseHelper : Component
    {
        private static MouseHelper instance;

        /// <summary>
        /// Gets the currently in use instance of the <see cref="MouseHelper"/> class.
        /// </summary>
        public static MouseHelper Instance
        {
            get
            {
                if (instance == null)
                    throw new InvalidOperationException("Initialize must be called before accessing the Instance property.");
                return instance;
            }
            private set
            {
                instance = value;
            }
        }

        /// <summary>
        /// Initializes the <see cref="GestureHelper"/> using the specified <see cref="Perovich.GameObjects.Interfaces.ICanHoldComponents"/>.
        /// </summary>
        /// <param name="game">The <see cref="Perovich.GameObjects.Interfaces.ICanHoldComponents"/> instance to use.</param>
        public static void Initialize(Interfaces.ICanHoldComponents game)
        {
            if (instance == null)
            {
                GameObject obj;
                while ((obj = game as GameObject) != null)
                    game = obj.Parent;
                Instance = new MouseHelper(game);
                game.Components.Add(Instance);

                Perovich.GameObjects.ComponentsExtensions.Helpers.Add(new Helper(Instance.GetType().GetMethod("Reset", System.Reflection.BindingFlags.Static)));
            }
        }

        /// <summary>
        /// Resets the <see cref="MouseHelper"/>. For use after the <see cref="Perovich.GameObjects.Interfaces.ICanHoldComponents"/> is no longer in use.
        /// </summary>
        public static void Reset()
        {
            Instance = null;
        }


        MouseHandler leftButton;

        /// <summary>
        /// Gets events for the left mouse button
        /// </summary>
        public static MouseHandler LeftButton
        {
            get { return Instance.leftButton; }
        }
        MouseHandler rightButton;

        /// <summary>
        /// Gets events for the right mouse button
        /// </summary>
        public static MouseHandler RightButton
        {
            get { return Instance.rightButton; }
        }
        MouseHandler middleButton;

        /// <summary>
        /// Gets events for the middle mouse button
        /// </summary>
        public static MouseHandler MiddleButton
        {
            get { return Instance.middleButton; }
        }



        private MouseHelper(Interfaces.ICanHoldComponents game)
            : base(game)
        {
            last_state = Mouse.GetState();
            leftButton = new MouseHandler();
            rightButton = new MouseHandler();
            middleButton = new MouseHandler();
        }

        MouseState last_state;

        /// <summary>
        /// Triggered when the mouse has moved.
        /// </summary>
        public event EventHandler<MouseHelperEventArgs> MouseDelta;
        /// <summary>
        /// Triggered when a mouse button state has changed.
        /// </summary>
        public event EventHandler<MouseHelperEventArgs> MouseChanged;


        /// <summary>
        /// Allows the <see cref="MouseHelper"/> to process its events.
        /// </summary>
        /// <param name="gameTime">A snapshot of the game's timing values.</param>
        public override void Update(GameTime gameTime)
        {
            var state = Mouse.GetState();

            if ((state.LeftButton != last_state.LeftButton)||
                (state.MiddleButton != last_state.MiddleButton) ||
                (state.RightButton != last_state.RightButton))
            {
                SignalMouseChanged(state, last_state);
            }

            if ((state.X != last_state.X) ||
                (state.Y != last_state.Y))
            {
                SignalMouseDelta(state, last_state);
            }

            if (state.LeftButton != last_state.LeftButton)
            {
                if (state.LeftButton == ButtonState.Pressed)
                    LeftButton.SignalDown(this, state, last_state);
                else
                    LeftButton.SignalUp(this, state, last_state);
            }

            if (state.RightButton != last_state.RightButton)
            {
                if (state.RightButton == ButtonState.Pressed)
                    RightButton.SignalDown(this, state, last_state);
                else
                    RightButton.SignalUp(this, state, last_state);
            }

            if (state.MiddleButton != last_state.MiddleButton)
            {
                if (state.MiddleButton == ButtonState.Pressed)
                    MiddleButton.SignalDown(this, state, last_state);
                else
                    MiddleButton.SignalUp(this, state, last_state);
            }

            last_state = state;
            base.Update(gameTime);
        }


        void SignalMouseChanged(MouseState state, MouseState lastState)
        {
            if (MouseChanged != null)
                MouseChanged(this, new MouseHelperEventArgs(state, lastState));
        }

        void SignalMouseDelta(MouseState state, MouseState lastState)
        {
            if (MouseDelta != null)
                MouseDelta(this, new MouseHelperEventArgs(state, lastState));
        }
    }

}
